package org.jjcm;

import java.util.Arrays;
import java.util.Comparator;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.CallLog;
import android.provider.LiveFolders;
import android.provider.CallLog.Calls;
import android.provider.Contacts.People;
import android.util.Log;

public class ContactsProvider extends ContentProvider {

	public static final String AUTHORITY = "org.jjcm.livefolders.contacts";

	// Uri that goes as input to the livefolder creation
	public static final Uri CONTACTS_URI = Uri.parse("content://" + AUTHORITY
			+ "/contacts");

	// To distinguish this URI
	private static final int TYPE_MY_URI = 0;
	private static final UriMatcher URI_MATCHER;
	static {
		URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
		URI_MATCHER.addURI(AUTHORITY, "contacts", TYPE_MY_URI);
		Log.d("stuff", "added the uri");
	}

	@Override
	public boolean onCreate() {
		return true;
	}

	@Override
	public int bulkInsert(Uri arg0, ContentValues[] values) {
		return 0; // nothing to insert
	}

	// Set of columns needed by a LiveFolder
	// This is the live folder contract
	private static final String[] CURSOR_COLUMNS = new String[] {
			BaseColumns._ID, LiveFolders.NAME, LiveFolders.DESCRIPTION,
			LiveFolders.INTENT, LiveFolders.ICON_PACKAGE,
			LiveFolders.ICON_RESOURCE, "Score" };

	// In case there are no rows
	// use this stand in as an error message
	// Notice it has the same set of columns of a live folder
	private static final String[] CURSOR_ERROR_COLUMNS = new String[] {
			BaseColumns._ID, LiveFolders.NAME, LiveFolders.DESCRIPTION };

	// The error message row
	private static final Object[] ERROR_MESSAGE_ROW = new Object[] { -1, // id
			"No contacts found", // name
			"Check your contacts database" // description
	};

	// The error cursor to use
	private static MatrixCursor sErrorCursor = new MatrixCursor(
			CURSOR_ERROR_COLUMNS);
	static {
		sErrorCursor.addRow(ERROR_MESSAGE_ROW);
	}

	// Columns to be retrieved from the contacts database
	private static final String[] CONTACTS_COLUMN_NAMES = new String[] {
			People._ID, People.DISPLAY_NAME, People.TIMES_CONTACTED,
			People.STARRED };

	private static final String[] CALLS_COLUMN_NAMES = new String[] {
			Calls._ID, Calls.DATE, Calls.CACHED_NAME, Calls.NUMBER };

	public Cursor query(Uri uri, String[] projection, String selection,
			String[] selectionArgs, String sortOrder) {
		// Figure out the uri and return error if not matching
		int type = URI_MATCHER.match(uri);
		if (type == UriMatcher.NO_MATCH) {
			return sErrorCursor;
		}

		Log.i("ss", "query called");
		Log.i("ss", uri.toString());
		if (projection != null) {
			for (String proj : projection) {
				Log.i("ss", proj);
			}
		}

		if (selection != null) {
			Log.i("ss", selection);
		}
		if (selectionArgs != null) {
			for (String arg : selectionArgs) {
				Log.i("ss", arg);
			}
		}
		if (sortOrder != null) {
			Log.i("ss", sortOrder);
		}

		try {
			MatrixCursor mc = loadNewData(this);
			mc.setNotificationUri(getContext().getContentResolver(), Uri
					.parse("content://contacts/people/"));
			MyCursor wmc = new MyCursor(mc, this);
			return wmc;
		} catch (Throwable e) {
			return sErrorCursor;
		}
	}

	public static MatrixCursor loadNewData(ContentProvider cp) {
		MatrixCursor mc = new MatrixCursor(CURSOR_COLUMNS);
		Cursor allContacts = null;
		Cursor allCalls = null;
		Cursor test = null;
		try {
			/*
			 * Ideally, this is what I'd enjoy being able to call, however the
			 * ContentResolver class doesn't allow raw queries.
			 */
			// allcontacts = cp.getContext().getContentResolver().rawQuery();

			// do a test
			try {
				test = cp.getContext().getContentResolver().query(
						CallLog.Calls.CONTENT_URI,
						CALLS_COLUMN_NAMES,
						"1) GROUP BY " + Calls.CACHED_NAME
								+ " ORDER BY COUNT( " + Calls.CACHED_NAME
								+ ") --", null, null);
				if (test != null) {
					Log.i("ss: test.getCount()", Integer.toString(test
							.getCount()));
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			// Grab all of my contacts
			allContacts = cp.getContext().getContentResolver().query(
					People.CONTENT_URI, CONTACTS_COLUMN_NAMES, null, // row
					// filter
					// 2
					null, People.TIMES_CONTACTED); // order by
			if (allContacts != null) {
				Log.i("ss: allContacts.getCount()", Integer
						.toString(allContacts.getCount()));
			}
			// Grab all of my recent calls
			allCalls = cp.getContext().getContentResolver().query(
					CallLog.Calls.CONTENT_URI, CALLS_COLUMN_NAMES, null, null,
					Calls.CACHED_NAME);

			if (allCalls != null) {
				Log.i("ss: allCalls()", Integer.toString(allCalls.getCount()));
			}
			/*
			 * Here's the comparator that I'm using to sort based on the "rank"
			 * that I assign each contact. The ranking is stored in the last
			 * column of each Object array as an integer.
			 */
			// A private class to use for comparison (utilized later in the
			// sort)
			class CompareRanking implements Comparator<Object[]> {

				public int compare(Object[] a1, Object[] a2) {
					int sortPos = CURSOR_COLUMNS.length - 1;
					Log.i("ss:sortPos", Integer.toString(sortPos));
					int a = Integer.parseInt(a1[sortPos].toString());
					if (a1 != null) {
						Log.i("ss:a", Integer.toString(a));
					}
					int b = Integer.parseInt(a2[sortPos].toString());
					if (a2 != null) {
						Log.i("ss:b", Integer.toString(b));
					}
					int r = b - a;
					Log.i("ss:return", Integer.toString(r));
					return r;
				}
			}

			// Initiate the data structure that we'll be using to store the
			// contacts until we sort them.
			Object[][] unsortedContacts = new Object[allContacts.getCount()][CURSOR_COLUMNS.length];

			// Run through each of the contacts and assign the rank for them.
			while (allContacts.moveToNext()) {
				int rank = rankContact(allContacts.getString(1), allCalls);
				String timesContacted = "Times contacted: "
						+ allContacts.getInt(2) + ". Rank: " + rank;

				Object[] rowObject = new Object[] {
						allContacts.getLong(0), // id
						allContacts.getString(1), // name
						timesContacted, // description
						Uri.parse("content://contacts/people/"
								+ allContacts.getLong(0)), // intent
						cp.getContext().getPackageName(), // package
						R.drawable.icon, // icon
						rank
				};

				// add our row (which is our ranked contact) into our array that
				// we'll be sorting
				unsortedContacts[allContacts.getPosition()] = rowObject;
			}

			if (unsortedContacts != null) {
				for (Object[] row : unsortedContacts) {
					Log.i("ss", row[1].toString());
				}
			}
			/*
			 * Here's the actual sort that it fails on. Uncommenting this will
			 * result in a live folder that just displays no contacts.
			 */
			Arrays.sort(unsortedContacts, new CompareRanking());

			// Add each element of our sorted array into our matrix, which we'll
			// later port into a cursor.
			for (Object[] o : unsortedContacts)
				mc.addRow(o);
			return mc;
		} finally {
			allContacts.close();
		}

	}

	public static int rankContact(String name, Cursor calls) {
		/*
		 * This call can be optimized by breaking out of the loop once three
		 * conditions are met: 1.) The list of calls is sorted by cached_name
		 * (already done) 2.) There has been a match against the given name
		 * variable 3.) Final condition: If those first two are true, then at
		 * any time if we encounter a name that doesn't match the name var, then
		 * we can break the loop.
		 * 
		 * We can further optimize it by moving backwards through the list of
		 * calls if the name starts with L-Z.
		 */
		int rank = 0;
		final int secondsInADay = 86400;
		calls.moveToFirst();
		while (calls.moveToNext()) {
			Log.i("ranking", "call name is equal to " + name);
			Log.i("ranking", "User name is equal to " + calls.getString(2));
			if (calls.getString(2) != null && calls.getString(2).equalsIgnoreCase( name ))
				// adds the number of seconds in a day, minus the number of
				// seconds between the times of each call into the day.
				rank += secondsInADay
						- ((secondsInADay
								+ secondsIntoTheDay(System.currentTimeMillis()) - secondsIntoTheDay(Long
								.parseLong(calls.getString(1)))) % secondsInADay);
		}
		Log.i("ranking", "number of calls used in calculation of rank: " + calls.getCount());
		Log.i("ranking", "rank for " + name + " is " + rank);
		return rank;
	}

	// strip milliseconds, then mod by the number of seconds in a day.
	private static int secondsIntoTheDay(long time) {
		return (int) ((time / 1000) % 86400);
	}

	@Override
	public String getType(Uri uri) {
		// indicates the MIME type for a given URI
		// targeted for this wrapper provider
		// This usually looks like
		// "vnd.android.cursor.dir/vnd.google.note"
		return People.CONTENT_TYPE;
	}

	public Uri insert(Uri uri, ContentValues initialValues) {
		throw new UnsupportedOperationException(
				"no insert as this is just a wrapper");
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		throw new UnsupportedOperationException(
				"no delete as this is just a wrapper");
	}

	public int update(Uri uri, ContentValues values, String selection,
			String[] selectionArgs) {
		throw new UnsupportedOperationException(
				"no update as this is just a wrapper");
	}
}